package sicnu.cs.ich.token.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import sicnu.cs.ich.common.util.CollectionUtil;
import sicnu.cs.ich.token.security.config.AppProperties;
import sicnu.cs.ich.token.security.config.TokenSecurity;
import sicnu.cs.ich.token.security.util.JwtUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author CaiKe
 * @date 2021/12/25 15:41
 */
//@Slf4j
@Component
@RequiredArgsConstructor
public class TokenFilter extends OrderedRequestContextFilter {

    private final ObjectMapper objectMapper;
    private final AppProperties appProperties;
    private final JwtUtil jwtUtil;
    private final TokenSecurity tokenSecurity;

    /**
     * 延迟加载Token
     *
     * @param request
     */
    public boolean lazyParseToken(HttpServletRequest request) {
        tokenSecurity.setTriedParseToken(true);
        // 检查Token完整性
        if (checkJwtToken(request)) {
            // 设置权限
            String token = request.getHeader(appProperties.getJwt().getHeader()).replace(appProperties.getJwt().getPrefix(), "");
            resolveToken(token).ifPresent(
                    claims -> {
                        // 保存userId
                        tokenSecurity.setPrinciple(Integer.parseInt(claims.getSubject()));
                        final Set<String> authorities = CollectionUtil.convertObjectToList(claims.get("authorities"))
                                .stream().map(String::valueOf)
                                .collect(Collectors.toSet());
                        // 设置权限
                        tokenSecurity.setAuthorities(authorities);
                        // 设置可信
                        tokenSecurity.assignCredible();
                    }
            );
        }
        return true;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        final String contextPath = request.getServletPath();
        // 校验权限（权限懒加载）
        if (tokenSecurity.preCheckAuthURI(contextPath)
                // 不满足permit，则判断是否是Deny
                || !tokenSecurity.preCheckDenyURI(contextPath)
                // 不是deny，解析Token
                && lazyParseToken(request)
                // 满足权限校验，放行
                && tokenSecurity.checkAuth(contextPath)) {
            // ###############【继续执行】########################
            filterChain.doFilter(request, response);
        } else
            this.writeErrorResponse(response, "拒绝访问！");
    }

    private void writeErrorResponse(HttpServletResponse servletResponse, String msg) {
        final var map = Map.of(
                "code", 401,
                "message", msg
        );
        try {
            final String str = objectMapper.writeValueAsString(map);
            servletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
            servletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
            servletResponse.getWriter().print(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 检查Token的完整性
     *
     * @param request
     * @return
     */
    private boolean checkJwtToken(HttpServletRequest request) {
        var header = request.getHeader(appProperties.getJwt().getHeader());
        return Strings.isNotEmpty(header) && header.startsWith(appProperties.getJwt().getPrefix());
    }


    /**
     * 解析Token，获取Authority
     *
     * @return
     */
    public Optional<Claims> resolveToken(String token) {
        try {
            return Optional.of(Jwts.parserBuilder().setSigningKey(jwtUtil.getKey()).build().parseClaimsJws(token).getBody());
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            // TODO Token过期应该单独判断并返回特定内容
            return Optional.empty();
        }
    }
}